<template>
  <div v-if="!isLoad" class="h-full">
    <Layout v-bind="$attrs" class="form-design-layout h-full">
      <LayoutSider class="left sider" width="280">
        <div><HTabs class="pl-4" v-model:active="controlTab" :dataSource="controlTypeList" /></div>
        <CollapseContainer
          class="mt-6"
          title="输入型控件"
          icon="triangle1|svg"
          :deg="45"
          :icon-style="{
            color: '#D8D8D8',
            width: '10px',
            height: '12px',
          }"
          canPreExpan
          :canExpan="false"
        >
          <CollapseItem
            :list="compMap[client].inputComponents"
            :handleListPushDrag="handleListPushDrag"
            @add-attrs="handleAddAttrs"
            @handle-list-push="handleListPush"
          />
        </CollapseContainer>
        <CollapseContainer
          title="选择型控件"
          icon="triangle1|svg"
          :deg="45"
          :icon-style="{
            color: '#D8D8D8',
            width: '10px',
            height: '12px',
          }"
          canPreExpan
          :canExpan="false"
        >
          <CollapseItem
            :list="compMap[client].selectComponents"
            :handleListPushDrag="handleListPushDrag"
            @add-attrs="handleAddAttrs"
            @handle-list-push="handleListPush"
          />
        </CollapseContainer>
        <CollapseContainer
          title="其他控件"
          icon="triangle1|svg"
          :deg="45"
          :icon-style="{
            color: '#D8D8D8',
            width: '10px',
            height: '12px',
          }"
          canPreExpan
          :canExpan="false"
        >
          <CollapseItem
            :list="compMap[client].otherComponents"
            :handleListPushDrag="handleListPushDrag"
            @add-attrs="handleAddAttrs"
            @handle-list-push="handleListPush"
          />

          <!-- @handle-list-push="handleListPush" -->
        </CollapseContainer>
      </LayoutSider>
      <LayoutContent>
        <Toolbar
          @handle-open-json-modal="handleOpenModal(jsonModal!)"
          @handle-open-import-json-modal="handleOpenModal(importJsonModal!)"
          @handle-open-code-modal="handleOpenModal(codeModal!)"
          @handle-clear-form-items="handleClearFormItems"
        />
        <FormComponentPanel
          :current-item="formConfig.currentItem"
          :data="formConfig"
          @handle-set-select-item="handleSetSelectItem"
        />
      </LayoutContent>
      <LayoutSider class="right sider" width="376">
        <HTabs
          class="pl-4"
          v-model:active="formConfig.activeKey"
          :dataSource="componentConfigTypeList"
        />
        <PropsPanel ref="propsPanel">
          <template v-for="item of formConfig.schemas[client]" #[`${item.component}Props`]="schema">
            <slot
              :name="`${item.component}Props`"
              v-bind="{ formItem: schema, props: schema.componentProps }"
            ></slot>
          </template>
        </PropsPanel>
      </LayoutSider>
    </Layout>

    <JsonModal ref="jsonModal" />
    <CodeModal ref="codeModal" />
    <ImportJsonModal ref="importJsonModal" />
  </div>
</template>

<script lang="ts" setup name="FormDesignPanel">
  import CollapseItem from './modules/CollapseItem.vue';
  import FormComponentPanel from './modules/FormComponentPanel.vue';
  import JsonModal from './components/JsonModal.vue';

  import { HTabs } from '/@/components/HTabs';
  import Toolbar from './modules/Toolbar.vue';
  import PropsPanel from './modules/PropsPanel.vue';
  import ImportJsonModal from './components/ImportJsonModal.vue';
  import CodeModal from './components/CodeModal.vue';

  import { ref, provide, Ref, PropType, nextTick, computed, watch } from 'vue';
  import { Layout, LayoutContent, LayoutSider } from 'ant-design-vue';

  import { IVFormComponent, IFormConfig, PropsTabKey } from '../../typings/v-form-component';
  import { formItemsForEach, generateKey } from '../../utils';
  import { cloneDeep } from 'lodash-es';
  import {
    controlTypeList,
    defFormData,
    compMap,
    componentConfigTypeList,
  } from '../../core/formItemConfig';
  import { useRefHistory, UseRefHistoryReturn } from '@vueuse/core';
  import { ComponentTypeEnum } from '/@/components/ModelTable/index';

  import { globalConfigState } from './config/formItemPropsConfig';
  import {
    IFormDesignMethods,
    CLIENT_TYPE_ENUM,
    IPropsPanel,
    IToolbarMethods,
  } from '../../typings/form-type';
  import { ModelFormConfig } from '../../../typing';
  import { CollapseContainer } from '/@/components/Container/index';

  import { useMessage } from '/@/hooks/web/useMessage';
  import { useFormDesignStoreWithOut } from '/@/store/modules/formDesign';

  const props = defineProps({
    title: {
      type: String,
      default: '表单设计器',
    },
    isCreate: {
      type: Boolean,
      default: false,
    },
    data: {
      type: Object as PropType<ModelFormConfig>,
      default: () => ({}),
    },
  });
  const { createMessage } = useMessage();
  const formDesignStore = useFormDesignStoreWithOut();
  const isLoad = ref(false);
  const controlTab = ref(0);

  const client = ref<number>(0);
  // 子组件实例
  const propsPanel = ref<null | IPropsPanel>(null);
  const jsonModal = ref<null | IToolbarMethods>(null);
  const importJsonModal = ref<null | IToolbarMethods>(null);

  const codeModal = ref<null | IToolbarMethods>(null);

  const formModel = ref({});

  // endregion
  const formConfig = computed({
    get() {
      return props.data.formDesignInfo || cloneDeep(defFormData);
    },
    set(v) {
      // eslint-disable-next-line vue/no-mutating-props
      props.data.formDesignInfo = v;
    },
  });

  const setFormConfig = (config: IFormConfig) => {
    //外部导入时，可能会缺少必要的信息。
    config.schemas = config.schemas || { [client.value]: [] };
    config.schemas[client.value].forEach((item) => {
      item.colProps = item.colProps || { span: 24 };
      item.componentProps = item.componentProps || {};
      item.itemProps = item.itemProps || {};
    });
    formConfig.value = config;
  };
  // 获取历史记录，用于撤销和重构
  // @ts-ignore
  const historyReturn = useRefHistory(formConfig, {
    deep: true,
    capacity: 20,
    parse: (val: IFormConfig) => {
      // 使用lodash.cloneDeep重新拷贝数据，把currentItem指向选中项
      const formConfig = cloneDeep(val);
      const { currentItem, schemas } = formConfig;
      // 从formItems中查找选中项

      const item =
        schemas[client.value] &&
        schemas[client.value].find((item) => item.field === currentItem?.field);
      // 如果有，则赋值给当前项，如果没有，则切换属性面板
      if (item) {
        formConfig.currentItem = item;
      }
      return formConfig;
    },
  });

  /**
   * 选中表单项
   * @param schema 当前选中的表单项
   * @param unValidate 是否校验
   */
  const handleSetSelectItem = async (schema: IVFormComponent, unValidate = false) => {
    if (schema && formConfig.value.currentItem?.field === schema.field) {
      return;
    }

    if (formConfig.value.currentItem?.component && !unValidate) {
      await currentFormValidate(changeFormControl.bind(null, schema));
    } else {
      changeFormControl(schema);
    }
  };

  async function changeFormControl(schema: IVFormComponent) {
    nextTick(() => {
      let typeEnum = ComponentTypeEnum;

      const typeField = 'componentType';

      schema[typeField] = typeEnum[schema.component];

      formConfig.value.currentItem = schema;

      handleChangePropsTabs(
        schema.field ? (formConfig.value.activeKey! === 0 ? 1 : formConfig.value.activeKey!) : 0,
      );
    });
  }
  async function currentFormValidate(callback = async () => {}) {
    let isError = true;

    try {
      await formDesignStore.getFormItemPropsRef?.validate();
      isError = false;
    } catch (e: any) {
      const text = '控件属性校验失败：' + e.errorFields[0].errors;
      createMessage.error(text);
      throw e;
    }
    if (isError) return isError;

    try {
      await formDesignStore.getFormComponentPropsRef?.validate();
      callback();
    } catch (e: any) {
      const text = '组件属性校验失败：' + e.errorFields[0].errors;
      createMessage.error(text);
      throw e;
    }
  }

  const setGlobalConfigState = (formItem: IVFormComponent) => {
    const span = formConfig.value.baseColProps.span;
    formItem.colProps = formItem.colProps || { span };
    formItem.colProps.span = globalConfigState.span;
  };

  /**
   * 添加属性
   * @param schemas
   * @param index
   */
  const handleAddAttrs = (_formItems: IVFormComponent[], _index: number) => {
    // const item = schemas[index];
  };

  const handleListPushDrag = (item: IVFormComponent) => {
    const formItem = cloneDeep(item);
    setGlobalConfigState(formItem);
    generateKey(formItem);

    return formItem;
  };
  /**
   * 单击控件时添加到面板中
   * @param item {IVFormComponent} 当前点击的组件
   */
  const handleListPush = (item: IVFormComponent) => {
    const formItem = cloneDeep(item);
    generateKey(formItem);

    if (!formConfig.value.currentItem?.field) {
      formConfig.value.schemas[client.value] &&
        formConfig.value.schemas[client.value].push(formItem);
      handleSetSelectItem(formItem);
      return;
    }
    handleCopy(formItem, false);
  };

  /**
   * 复制表单项，如果表单项为栅格布局，则遍历所有自表单项重新生成key
   * @param {IVFormComponent} formItem
   * @return {IVFormComponent}
   */
  const copyFormItem = (formItem: IVFormComponent) => {
    const newFormItem = cloneDeep(formItem);
    if (newFormItem.component === 'Grid') {
      formItemsForEach([formItem], (item) => {
        generateKey(item);
      });
    }
    return newFormItem;
  };
  /**
   * 复制或者添加表单，isCopy为true时则复制表单
   * @param item {IVFormComponent} 当前点击的组件
   * @param isCopy {boolean} 是否复制
   */
  const handleCopy = async (
    item: IVFormComponent = formConfig.value.currentItem as IVFormComponent,
    isCopy = true,
  ) => {
    try {
      await currentFormValidate();

      const key = formConfig.value.currentItem?.field;

      /**
       * 遍历当表单项配置，如果是复制，则复制一份表单项，如果不是复制，则直接添加到表单项中
       * @param schemas
       */
      const traverse = (schemas: IVFormComponent[]) => {
        // 使用some遍历，找到目标后停止遍历
        schemas.some((formItem: IVFormComponent, index: number) => {
          if (formItem.field === key) {
            // 复制 或者 添加
            isCopy
              ? schemas.splice(index, 0, copyFormItem(formItem))
              : schemas.splice(index + 1, 0, item);
            const event = {
              newIndex: index + 1,
            };

            // 添加到表单项中
            handleBeforeColAdd(event, schemas, isCopy);
            return true;
          }
          if (['Grid', 'Tabs'].includes(formItem.component)) {
            // 栅格布局
            formItem.columns?.forEach((item) => {
              traverse(item.children);
            });
          }
        });
      };
      if (formConfig.value.schemas[client.value]) {
        traverse(formConfig.value.schemas[client.value]);
      }
    } catch (e) {
      throw e;
    }
  };

  /**
   * 添加到表单中
   * @param newIndex {object} 事件对象
   * @param schemas {IVFormComponent[]} 表单项列表
   * @param isCopy {boolean} 是否复制
   */
  const handleBeforeColAdd = ({ newIndex }: any, schemas: IVFormComponent[], isCopy = false) => {
    const item = schemas[newIndex];
    isCopy && generateKey(item);
    handleSetSelectItem(item);
  };

  /**
   * 打开模态框
   * @param Modal {IToolbarMethods}
   */
  const handleOpenModal = (Modal: IToolbarMethods) => {
    const config = cloneDeep(formConfig.value);
    Modal?.showModal(config);
  };
  /**
   * 切换属性面板
   * @param key
   */
  const handleChangePropsTabs = (key: PropsTabKey) => {
    formConfig.value.activeKey = key;
  };
  /**
   * 清空表单项列表
   */
  const handleClearFormItems = () => {
    formConfig.value.schemas[client.value] = [];
    handleSetSelectItem({ component: '', event: {}, componentProps: {} });
  };

  const setFormModel = (key, value) => (formModel.value[key] = value);

  init();
  watch(
    () => props.data.client,
    () => {
      init();
    },
  );
  async function init() {
    isLoad.value = true;
    client.value = props.data.client ?? CLIENT_TYPE_ENUM.PC;
    const formInfo = props.data.formDesignInfo || {};

    const schemas = formInfo.schemas || { [client.value]: [] };
    const labelSpan = props.data.client === CLIENT_TYPE_ENUM.APP ? 5 : 2;

    formConfig.value = {
      // 表单配置
      size: 'default',
      schemas,
      title: formInfo.title,
      isReadonly: formInfo.isReadonly,
      layout: formInfo.layout || 'horizontal',
      // labelLayout: formInfo.labelLayout || 'Grid',
      labelWidth: formInfo.labelWidth ?? 100,
      labelAlign: formInfo.labelAlign || 'right',
      hideRequiredMark: formInfo.hideRequiredMark ?? false,
      labelCol: formInfo.labelCol || { span: labelSpan },
      wrapperCol: formInfo.wrapperCol || { span: 24 },
      colon: formInfo.colon ?? false,
      baseColProps: formInfo.baseColProps || {
        span: 24,
      },
      client: client.value,
      btns: formInfo.btns || [],
      event: formInfo.event || {},
      currentItem: {
        component: '',
        event: {},
        colProps: {
          span: 24,
        },
        componentProps: {},
      },
      activeKey: 0,
      modifyLogConfig: formInfo.modifyLogConfig || {},
    };

    if (schemas[client.value].length) {
      handleSetSelectItem(schemas[client.value][0]);
    }
    isLoad.value = false;
  }

  provide('formModel', formModel);
  provide('modelId', props.data.modelId);
  // 把祖先组件的方法项注入到子组件中，子组件可通过inject获取
  provide<(key: String, value: any) => void>('setFormModelMethod', setFormModel);
  // region 注入给子组件的属性
  // provide('currentItem', formConfig.value.currentItem)

  // 把表单配置项注入到子组件中，子组件可通过inject获取，获取到的数据为响应式
  provide<Ref<IFormConfig>>('formConfig', formConfig);
  provide<Ref<number>>('client', client);

  // 注入历史记录
  provide<UseRefHistoryReturn<any, any>>('historyReturn', historyReturn);

  // 把祖先组件的方法项注入到子组件中，子组件可通过inject获取
  provide<IFormDesignMethods>('formDesignMethods', {
    handleBeforeColAdd,
    handleCopy,
    handleListPush,
    handleSetSelectItem,
    handleAddAttrs,
    setFormConfig,
  });

  defineExpose({ currentFormValidate });
  // endregion
</script>

<style lang="less" scoped>
  //  @prefix-cls: ~'@{namespace}-collapse-container';

  .form-design-layout {
    .sider {
      //需要设置背景色，让主题生效
      background-color: @component-background;
    }

    ::v-deep(.h-tabs) {
      .h-tab {
        color: #999;

        &-active {
          font-weight: 600 !important;
        }
      }
    }
    ::v-deep(.@{namespace}-collapse-container__header) {
      border-bottom: transparent;

      .title {
        color: #333;
        font-weight: 600;
      }
    }
  }
</style>
